package com.alogic.pipeline;

import com.alogic.xscript.Logiclet;
import com.alogic.xscript.LogicletContext;
import com.alogic.xscript.Script;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.doc.json.JsonObject;
import com.anysoft.util.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 缺省实现
 *
 * @since 1.6.13.35 [20210218 duanyy] <br>
 *
 * @version 1.6.13.37 [20200223 duanyy] <br>
 * - 修正状态取值问题 <br>
 *
 * @version 1.6.13.39 [20210225 duanyy] <br>
 * - 增加on-prepare事件支持；
 *
 * @version 1.6.15.3 [20211112 duanyy] <br>
 * - 优化pipeline的tolerable机制 <br>
 */
public class DefaultPipeline extends PipelineStage.Abstract implements PipelineListener, Pipeline {
    /**
     * stages
     */
    protected List<PipelineStage> stages = new ArrayList<PipelineStage>();
    protected Logiclet onPrepare = null;
    protected Logiclet onClean = null;

    @Override
    public void prepare(String jobId, Properties ctx, PipelineListener listener) {
        this.listener = listener;
        String prefix = PropertiesConstants.transform(ctx,$prefix,"pp.");

        for (Pair<String,String> pair:pairs){
            ctx.SetValue(prefix + pair.key(),pair.value());
        }

        ctx.SetValue(prefix + "id",getId());
        ctx.SetValue("runId",jobId);

        if (onPrepare != null){
            LogicletContext logicletContext = new LogicletContext(ctx);
            try {
                XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                onPrepare.execute(doc,doc, logicletContext, null);
                List<Pair<String,String>> env = new ArrayList<Pair<String,String>>();
                logicletContext.list(env);
                for (Pair<String,String> pair:env){
                    ctx.SetValue(pair.key(),pair.value());
                }
            }catch (Exception ex){
                LOG.info("Failed to prepare job:" + ExceptionUtils.getStackTrace(ex));
            }
        }
    }

    @Override
    public void configure(Properties p) {
        super.configure(p);
    }

    @Override
    public void run(String jobId, Properties ctx, PipelineListener listener) {
        onCreate(jobId,ctx);
        onStart(jobId,ctx);
        try{
            List<StageContext> stageContexts = new ArrayList<StageContext>();
            int order = 0;
            for (PipelineStage stage:stages){
                String stageRunId = newId();
                StageContext stageContext = new StageContext(stageRunId,ctx,stage);
                stageContext.SetValue("runOrder",String.valueOf(order++));
                stageContext.SetValue("stageRunId",stageRunId);
                stage.prepare(jobId,stageContext,listener);
                onCreate(jobId,stageRunId,stageContext);
                stageContexts.add(stageContext);
            }

            boolean canceled = false;
            boolean allOk = true;
            for (StageContext stageContext:stageContexts){
                if (isCanceled(jobId)){
                    canceled = true;
                    break;
                }
                String id = stageContext.getId();
                PipelineStage stage = stageContext.getStage();
                onStart(jobId,id,stageContext);
                stage.run(jobId,stageContext,listener);
                onFinish(jobId,id,stageContext);

                String stageStage =
                        PropertiesConstants.getString(stageContext,"$state",
                                "ok",true);
                if (!stageStage.equals("ok") && !stage.isFaultTolerable()){
                    ctx.SetValue("$state","failed");
                    ctx.SetValue("$stateMessage", PropertiesConstants.getString(stageContext,"$stateMessage",
                            "failed",true));
                    allOk = false;
                    break;
                }
            }

            if (canceled){
                ctx.SetValue("$state", "canceled");
                ctx.SetValue("$stateMessage", "canceled");
            }else {
                if (allOk) {
                    ctx.SetValue("$state", "ok");
                    ctx.SetValue("$stateMessage", "ok");
                }
            }
        }catch (Exception ex){
            ctx.SetValue("$state","failed");
            ctx.SetValue("$stateMessage",ex.getMessage());
        }finally {
            if (onClean != null){
                LogicletContext logicletContext = new LogicletContext(ctx);
                try {
                    XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                    onClean.execute(doc,doc, logicletContext, null);
                }catch (Exception ex){
                    LOG.info("Failed to clean job:" + ExceptionUtils.getStackTrace(ex));
                }
            }
            onFinish(jobId, ctx);
        }
    }


    @Override
    public void configure(Element e, Properties p) {
        XmlElementProperties props = new XmlElementProperties(e,p);
        $prefix = "pp.";
        configure(props);
        this.loadPairs(e,p);

        NodeList nodeList = XmlTools.getNodeListByPath(e,"stage");
        Factory<PipelineStage> f = new Factory<PipelineStage>();
        for (int i = 0 ;i < nodeList.getLength() ; i ++){
            Node node = nodeList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE){
                continue;
            }
            Element elem = (Element) node;
            try {
                PipelineStage stage = f.newInstance(elem,props,"module");
                stages.add(stage);
            }catch (Exception ex){
                LOG.error("Can not create pipeline stage:" + XmlTools.node2String(elem));
            }
        }

        Element elem = XmlTools.getFirstElementByPath(e, "on-prepare");
        if (elem != null){
            onPrepare = Script.create(elem, props);
        }

        elem = XmlTools.getFirstElementByPath(e, "on-clean");
        if (elem != null){
            onClean = Script.create(elem, props);
        }
    }
}